home *** CD-ROM | disk | FTP | other *** search
/ Amiga Plus 2004 #2 / Amiga Plus CD - 2004 - No. 02.iso / AmigaPlus / Tools / Development / AmigaTalk / general / PluggableAdaptor.st < prev    next >
Encoding:
Text File  |  2004-01-31  |  8.7 KB  |  241 lines

  1. " --------------------------------------------------------------------
  2.   I provide a level of indirection between a View/Controller and an 
  3.   underlying model.  The View and Controller send my instances the 
  4.   standard messages value and value:, which I convert into arbitrary 
  5.   actions defined by blocks.
  6.  
  7.   Instance Variables:
  8.     model    <ValueModel>  the underlying model (only used for 
  9.                               dependency and for isActive testing)
  10.     getBlock    <BlockClosure>  evaluate this block to get the value
  11.     putBlock    <BlockClosure>  evaluate this block to set the value
  12.     updateBlock    <BlockClosure>  evaluate this block to handle 
  13.                                         an update from the model; if it 
  14.                                         returns true, notify our dependents
  15.  
  16.   The getBlock is evaluated with one argument, the model.  
  17.   The putBlock is evaluated with two arguments, the model and the 
  18.   new value.  The updateBlock is evaluated with three arguments, 
  19.   the model, the update aspect, and the update parameter.
  20.  
  21.   We use blocks rather than selectors because blocks are much more 
  22.   flexible than selectors for representing encapsulated behavior.  
  23.   They can reference more than one object, and they can include 
  24.   embedded parameters such as a collection index.
  25.  
  26.  
  27.   Object Reference:
  28.     A PluggableAdaptor is the most flexible of the value models, 
  29.     because its activities are highly configurable.  This flexibility 
  30.     comes at the cost of a certain conceptual complexity, however. 
  31.     At one time, PluggableAdaptor was the only value model -- now, 
  32.     more convenient value models exist for the most common situations 
  33.     in which a PluggableAdaptor was formerly applied.  In particular, 
  34.     the subclass TypeConverter performs several of the most common 
  35.     conversions from one type of object to another, such as converting 
  36.     a number to a string and back again.  Still, demanding situations 
  37.     remain in which a PluggableAdaptor is the most economical solution.
  38.   
  39.   A PluggableAdaptor has a model, which can either be an application 
  40.   model or a domain model, from which it obtains the desired data value. 
  41.   The adaptor is configured via three blocks, which enable it to 
  42.   perform customized actions at three junctures in the flow of 
  43.   communications between the dependent (typically a widget) and the 
  44.   model.  The first block, the getBlock, controls what happens when a 
  45.   value is requested (via #value).  The block takes one argument, 
  46.   the model.  The block returns the value, after fetching it from 
  47.   the model and applying any necessary computations or transformations. 
  48.   For example, the following getBlock fetches an accountNumber from 
  49.   the model, converts it to a string and pads it with leading zeroes: 
  50.  
  51.     [ :model | 
  52.         | paddedString | 
  53.         paddedString <- model accountNumber printString. 
  54.         6 - paddedString size 
  55.             timesRepeat: [paddedString <- '0', paddedString]. 
  56.         paddedString] 
  57.  
  58.    The second block, the putBlock, controls what happens when a value 
  59.    is stored (via #value:).  The block takes two arguments, the model 
  60.    and the value to be stored.  The block stores the value in the 
  61.    model after applying any necessary computations or transformations. 
  62.    For example, the following putBlock converts a padded 
  63.    accountNumber string back into a number and stores the number 
  64.    in the model: 
  65.     
  66.         [ :model :val | 
  67.         model accountNumber: val asNumber]. 
  68.  
  69.    The third block, the updateBlock, controls what happens when the 
  70.    adaptor receives an #update:with:from: message.  It receives that 
  71.    message whenever the model sends a variant of #changed:with: to 
  72.    itself -- in the accountNumber example, the model would send such 
  73.    a message when its accountNumber had been changed.  The block takes 
  74.    three arguments: the model and the first two arguments from the 
  75.    #update:with: message (known as the update aspect and the update 
  76.    parameter).  The block returns true or false, usually after testing 
  77.    the aspect to see whether the adaptor cares about that type of 
  78.    change in the model.  When the updateBlock returns true, the 
  79.    adaptor's getBlock is invoked to update the widget's value.  When 
  80.    the updateBlock returns false, no action is taken.  For example, 
  81.    the following updateBlock causes the widget to refetch the value 
  82.    only when the update aspect is #accountNumber and the parameter 
  83.    (an accountNumber string) is less than 1000: 
  84.  
  85.     [ :model :aspect :parameter | 
  86.         aspect == #accountNumber and: [parameter asNumber < 1000]]. 
  87.  
  88.    A PluggableAdaptor is created by sending #on: to this class, 
  89.    with the model as the argument.  The three blocks are then 
  90.    initialized via #getBlock:putBlock:updateBlock.  The protocol 
  91.    named 'initialize algorithm' contains shortcuts for configuring the 
  92.    three blocks for common situations. 
  93.   ----------------------------------------------------------------------
  94. "
  95.  
  96. Class PluggableAdaptor :ValueModel ! model getBlock putBlock updateBlock !
  97. [
  98.    on: aModel
  99.      ^ self new model: aModel
  100. |
  101.    getBlock: aBlock1 putBlock: aBlock2 updateBlock: aBlock3
  102.      " Set the blocks used for dealing with the model. "
  103.  
  104.      getBlock    <- aBlock1.
  105.      putBlock    <- aBlock2.
  106.      updateBlock <- aBlock3
  107. |
  108.    initialize
  109.      super initialize.
  110.  
  111.      " Initialize the blocks on the assumption that the 
  112.      * underlying model is a ValueModel.  This is wrong, 
  113.      * of course. 
  114.      "
  115.      self getBlock: [:m | m value]
  116.           putBlock: [:m :v | m value: v]
  117.        updateBlock: [:m :a :p | a == #value ]
  118. |
  119.    model: aModel
  120.      model removeDependent: self.
  121.      
  122.      model <- aModel.
  123.      
  124.      (super dependents notNil)
  125.         ifTrue: [model addDependent: self]
  126. |
  127.    subjectChannel: aValueHolder
  128.  
  129.      self model: aValueHolder
  130. |
  131.    collectionIndex: index
  132.      " Initialize the receiver to access
  133.      * the given element of a collection
  134.      * that is the value of the model. 
  135.      "
  136.      self
  137.          getBlock: [:m | m value at: index]
  138.          putBlock: [:m :v | m value at: index put: v.
  139.                             m changed: #at with: index]
  140.  
  141.       updateBlock: [:m :a :p | a == #value or: [a == #at and: [p = index]]]
  142. |
  143.    getSelector: aSymbol0 putSelector: aSymbol1
  144.      " Initialize the receiver to act like
  145.      * the old pluggable classes. 
  146.      "
  147.      self
  148.            getBlock: [:m | m perform: aSymbol0]
  149.            putBlock: [:m :v | m perform: aSymbol1 with: v]
  150.         updateBlock: [:m :a :p | a == #value or: [a == aSymbol0]]
  151. |
  152.    performAction: aSelector
  153.      " Initialize the receiver to perform the action
  154.      * when assigned a value 
  155.      "
  156.  
  157.      self
  158.            getBlock: [:m | false]
  159.            putBlock: [:m :v | m perform: aSelector]
  160.         updateBlock: [:m :a :p | false]
  161. |
  162.    selectValue: aValue  ! cacheValue !
  163.      " Initialize the receiver to act like a Boolean 
  164.      * that is true when the model's value is equal to aValue. 
  165.      "
  166.      cacheValue <- nil.
  167.  
  168.      self
  169.            getBlock: [:m | cacheValue <- m value = aValue]
  170.            putBlock: [:m :v | (v)
  171.                                ifTrue: [m value: aValue]
  172.                               ifFalse: [m value = aValue ifTrue: [m value: nil]]]
  173.         updateBlock: [:m :a :p | ((m value = aValue) = cacheValue)
  174.                                    ifFalse: [ ^ true ]
  175.                                     ifTrue: [ ^ false] ]
  176. |
  177.    model
  178.      ^ model
  179. |
  180.    setValue: newValue
  181.      putBlock value: model value: newValue
  182. |
  183.    value
  184.      ^ getBlock value: model
  185. |
  186.    valueUsingSubject: aSubject
  187.  
  188.      aSubject == nil ifTrue: [^nil].
  189.      
  190.      ^ getBlock value: aSubject
  191. |
  192.    update: aspect with: parameter from: sender
  193.  
  194.      (updateBlock value: model value: aspect value: parameter)
  195.          ifTrue: [self changed: #value ]
  196. |
  197.    addDependent: aDependent 
  198.  
  199.      (super dependents isNil) 
  200.         ifTrue: [model addDependent: self].
  201.      
  202.      super addDependent: aDependent.
  203. |
  204.    removeDependent: aDependent 
  205.  
  206.      super removeDependent: aDependent.
  207.  
  208.      (super dependents isNil) 
  209.         ifTrue: [model removeDependent: self]
  210. |
  211.    isProtocolAdaptor
  212.      " Answer as to whether the receiver transduces
  213.      * protocol into ValueModel protocol.
  214.      "
  215.      ^ true
  216. |
  217.    makeAdaptorForRenderingStoreLeafInto: pair
  218.  
  219.      pair at: 1 put: self.
  220.      ^ (model isProtocolAdaptor)
  221.          ifTrue: [model <- model copy.
  222.                            model makeAdaptorForRenderingStoreLeafInto: pair]
  223.         ifFalse: [pair]
  224. |
  225.    renderingValueUsingSubject: aSubject ! pair aCopy cell !
  226.  
  227.      (model isProtocolAdaptor)
  228.         ifFalse: [ ^ self valueUsingSubject: aSubject ].
  229.      
  230.      aCopy <- self copy.
  231.      pair  <- Array new: 2.
  232.      
  233.      pair at: 2 put: aCopy.
  234.      
  235.      cell <- aCopy makeAdaptorForRenderingStoreLeafInto: pair.
  236.      
  237.      cell first subjectChannel: aSubject asValue.
  238.      
  239.      ^ cell last value
  240. ]
  241.